﻿using System;
using System.Collections.Generic;
using ComponentModel = System.ComponentModel;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows;
using System.Windows.Threading;
using System.Threading;
using Net.Chirash.Twitter.TweetZone.WindowComponents.ViewModels;
using Net.Chirash.Twitter.TweetZone.WindowComponents.Containers;

namespace Net.Chirash.Twitter.TweetZone.WindowComponents
{
    /// <summary>
    /// Container(View)とそのViewModelを管理と、正しいDispatcherでViewの生成を行うクラスです。
    /// </summary>
    public class ContainerComposer
        : ComponentModel.INotifyPropertyChanged, IDisposable
    {
        #region constructors

        /// <summary>
        /// 指定された関数を用いて管理するContainerComposerを生成します。
        /// </summary>
        /// <param name="Create">Viewを生成する関数</param>
        /// <param name="Dispose">Viewを破棄する関数</param>
        /// <param name="ViewModel">Viewに渡すViewModel</param>
        public ContainerComposer(Func<ContainerViewModelBase, Container> Create, Action<Container> Dispose, ContainerViewModelBase ViewModel)
        {
            this.Create = Create;
            this.DisposeFunc = Dispose;
            this.ContainerViewModel = ViewModel;
            this.ContainerViewModel.Composer = this;
        }

        /// <summary>
        /// デフォルトの関数と、指定された破棄関数 を用いて管理するContainerComposerを生成します。
        /// </summary>
        /// <param name="ContainerType">
        ///     <para>Viewに使用されるContainerの型。Containerを継承する型を指定してください。</para>
        ///     <para>デフォルトの生成関数は、指定されたViewModelだけを受け取るコンストラクタ、または引数なしコンストラクタを呼び出します。</para>
        /// </param>
        /// <param name="Dispose">Viewを破棄する関数</param>
        /// <param name="ViewModel">Viewに渡すViewModel</param>
        public ContainerComposer(Type ContainerType, Action<Container> Dispose, ContainerViewModelBase ViewModel)
            : this(defaultCreateFunc(ContainerType, ViewModel.GetType()), Dispose, ViewModel)
        { }

        /// <summary>
        /// デフォルトの関数を用いて管理するContainerComposerを生成します。
        /// </summary>
        /// <param name="ContainerType">
        ///     <para>Viewに使用されるContainerの型。Containerを継承する型を指定してください。</para>
        ///     <para>デフォルトの生成関数は、指定されたViewModelだけを受け取るコンストラクタ、または引数なしコンストラクタを呼び出します。また、破棄関数はContainerに対して何も行いません。
        /// </para>
        /// </param>
        /// <param name="ViewModel">Viewに渡すViewModel</param>
        public ContainerComposer(Type ContainerType, ContainerViewModelBase ViewModel)
            : this(ContainerType, defaultDisposeFunc(), ViewModel)
        { }

        #endregion

        #region public Properties

        private Container _cntView;
        public Container View
        {
            get
            {
                return _cntView;
            }
            private set
            {
                _cntView = value;

                OnPropertyChanged("View");
                OnPropertyChanged("IsViewCreated");
            }
        }

        private ContainerViewModelBase _containerViewModel;
        public ContainerViewModelBase ContainerViewModel
        {
            get
            {
                return _containerViewModel;
            }
            set
            {
                _containerViewModel = value;

                if (IsViewCreated)
                    View.DataContext = value;
                OnPropertyChanged("ContainerViewModel");
            }
        }

        public bool IsViewCreated { get { return this.View != null; } }

        #region public Dispatcher AssociatedDispatcher { get; private set; }
        private Dispatcher _AssociatedDispatcher;
        public Dispatcher AssociatedDispatcher
        {
            get { return _AssociatedDispatcher; }
            private set { _AssociatedDispatcher = value; OnPropertyChanged("AssociatedDispatcher"); }
        }
        #endregion

        #endregion

        #region public func

        /// <summary>
        /// 指定されたDispatcher内でViewを生成します。
        /// </summary>
        /// <param name="dispatcher">使用するDispatcher</param>
        /// <exception cref="InvalidOperationException">Viewがすでに作られているときに投げられます。DisposeViewを呼び出しているか確認してください。</exception>
        /// <exception cref="ArgumentException">ContainerComposerのコンストラクタでTypeを指定した場合、
        /// その型がContainerの派生クラスでない、適切なコンストラクタがない、もしくは、それがpublicでない時に投げられます。</exception>
        public void CreateView(Dispatcher dispatcher)
        {
            if (View != null)
                throw new InvalidOperationException("すでにViewは作られています");

            Container tmp = null;
            var processFunc = new Action(() =>
            {
                tmp = Create(this.ContainerViewModel);
            });

            this.AssociatedDispatcher = dispatcher;
            SafeInvoke(processFunc);

            this.View = tmp;
        }

        /// <summary>
        /// Viewを破棄します。
        /// </summary>
        public void DisposeView()
        {
            if (View != null)
            {
                if (this.AssociatedDispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId)
                    DisposeFunc(this.View);
                else
                    this.AssociatedDispatcher.Invoke(DisposeFunc, this.View);

                this.AssociatedDispatcher = null;
                this._cntView.DataContext = null;
                this._cntView = null;
            }
        }

        /// <summary>
        /// 関連付けられたDispatcherのスレッドでActionを実行します。
        /// </summary>
        /// <param name="act">実行するAction</param>
        public void SafeInvoke(Action act)
        {
            if (this.AssociatedDispatcher == null
                || this.AssociatedDispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId)
                act();
            else
                this.AssociatedDispatcher.Invoke(act);
        }

        #endregion

        #region private members

        private Func<ContainerViewModelBase, Container> Create;
        private Action<Container> DisposeFunc;

        private static Func<ContainerViewModelBase, Container> defaultCreateFunc(Type typeV, Type typeVM)
        {
            var constInfo = typeV.GetConstructor(new Type[] { typeVM });

            if (constInfo == null)
                constInfo = typeV.GetConstructor(Type.EmptyTypes);

            if (!typeV.IsSubclassOf(typeof(Container)) || constInfo == null || !constInfo.IsPublic)
                new ArgumentException("適切なコンストラクタ、または引数が空のコンストラクタがTypeContainerに存在しない、またはpublicでない、Containerが規定クラスでないのでViewをCreateできません");

            var retFunc = new Func<ContainerViewModelBase, Container>((vm) =>
            {
                if (constInfo.GetParameters().Any())
                    return constInfo.Invoke(new object[] { vm }) as Container;
                else
                {
                    var cnt = constInfo.Invoke(new object[0]) as Container;
                    cnt.DataContext = vm;
                    return cnt;
                }
            });
            return retFunc;
        }

        private static Action<Container> defaultDisposeFunc()
        {
            return new Action<Container>((cnt) => { });
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        public event ComponentModel.PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new ComponentModel.PropertyChangedEventArgs(name));
        }

        #endregion

        public void Dispose()
        {
            GC.SuppressFinalize(this);
            if (this.ContainerViewModel != null)
            {
                this.ContainerViewModel.Composer = null;
            }
        }

        ~ContainerComposer()
        {
            ((IDisposable)this).Dispose();
        }
    }
}
